######################################################################
# Time-stamp: <05/06/17 18:06:48 ostolop>
#
# Routines to build EPC query XML documents
######################################################################
package EP::Common::Query::QueryBuilder;
use strict;

use CGI;
use XML::LibXML;

use EP::Config;
use File::Temp;

=head1 NAME

EP_UI_XML::QueryBuilder - build the EP query XML document from an HTTP
request (via CGI.pm)

=head1 SYNOPSIS

C<my $ep_query_doc = QueryBuilder::build_query ( $query, $source_doc );>

Here C<$query> is an CGI query object, and C<$source_doc> is an XML document
with the EPC XML for the components to be rendered.

=head1 DESCRIPTION

The document is rooted with C<ep_query>, which has the
C<render_component_ids> attribute, listing (comma-separated) the
component ids to be rendered for this query.

This, in particular, allows for GET requests like
C<http://localhost/EP_XML_UI/cgi-bin/start_ui.pl?render_component_ids=1,2>
to come in, creating an empty query C<<ep_query
render_component_ids="1,2"/>>, rendering components with ids 1 and 2.

=head1 METHODS

=over

=item build_query ( query, source )

Parses the CGI query (first argument), with regard to the source EPC
XML, and returns an XML query document.

Will also validate the query, on a per-component basis (how???) and if
query is valid, set the  C<is_valid> attribute of C<<component>> to 1,
otherwise to 0 and create an attribute C<err_code> with an error code
corresponding to a predefined error code (where???).

=over

=item Note (1)

each query can have only one error per component!

=item Note (2)

should we plan for multiple validation errors??

=back

=back

=cut

chomp ( my $date = `date` );

my $ep_query_XML = <<__EPQUERYXML__;
<?xml version="1.0"?><ep_query/>
__EPQUERYXML__

sub build_query {
  my ($cgi_query, $source_doc, $QV, $session) = @_;
  my $doc;

  my $parser = XML::LibXML->new();
  $doc = $parser->parse_string ( $ep_query_XML );

  if ($@) {
    error($@);
    die "Couldn't create document.";
  }

  my $root	      = $doc->getDocumentElement();
  my $active_comp_id  = $cgi_query->param("active_component_id");

  my $render_component_ids = $cgi_query->param("render_component_ids") || "";
  $root->setAttribute ( "render_component_ids", $render_component_ids );

  my $session_id = $session->{_session_id};
  $root->setAttribute( "session_id", $session_id );

  foreach ( $cgi_query->param ("component") ) {
    # only build the query for those components present in the source!
    if ( not $source_doc->findvalue( '//component[@id='.$_.']/@id' ) eq "" ) {
      $root->appendChild ( create_component ( $doc, $cgi_query, $_, $active_comp_id, $source_doc, $QV ) );
    }
  }

  return $doc;
}

sub create_component {
  my ( $doc, $cgi_query, $compid, $active_comp_id, $source_doc, $QV ) = @_;
  my $component = $doc->createElement ( "component" );

  # If this is the active component, the "active_component_id" passed
  # from the form will coincide with its id. The active component is
  # the one, on which the "Proceed" (submit) button was pressed
  my $is_active = 0;
  $is_active = 1 if $compid eq $active_comp_id;

  $component->setAttribute( "id"      , $compid );
  $component->setAttribute( "display" , $cgi_query->param ( "display_component_".$compid ) );
  $component->setAttribute( "active"  , $is_active );

  my $comp_targets = $cgi_query->param ( "targets___c_".$compid );
  if ( $comp_targets ) {
    my @ct = split /,/, $comp_targets;
    my $target_sequence = create_target_sequence ( $doc, $compid, \@ct, $source_doc );
    $component->appendChild ( $target_sequence ) if defined $target_sequence;
  }

  foreach ( $cgi_query->param ( "section___c_".$compid ) ) {
    $component->appendChild ( create_section ( $doc, $cgi_query, $compid, $_, $source_doc ) );
  }

  # check missing nodes on subsection inputs and section inputs (submits)
  my $subsec_missing_nodes_count = $component->find (
     'section/subsection/submit[../@id=ancestor::section/@active_subsection_id and @missing=1]'
						    )->size();

  my $sec_missing_nodes_count = $component->find('section/submit[@missing=1]')->size();

  my $err_code = "";
  if ( ( $subsec_missing_nodes_count > 0) || ($sec_missing_nodes_count > 0) ) {
    $err_code = "Some required fields are missing!";
  } else {
    # only do any real validation if nothing required is missing...
    if ( $component->findvalue ( '@active' ) == 1 ) {
      $err_code = $QV->validate($compid, $component, $source_doc);
    }
  }

  if ( $err_code eq "" ) {
    $component->setAttribute ( "is_valid", 1 );
  } else {
    $component->setAttribute ( "is_valid", 0 );
    $component->setAttribute ( "err_code", $err_code );
  }

  return $component;
}

sub create_target_sequence {
  my ( $doc, $compid, $target_list, $source_doc ) = @_;
  my $target_sequence = $doc->createElement ( "target_sequence" );

  foreach my $target_value ( @{$target_list} ) {
    my ( $target_comp_id, $target_owner_comp_id, $target_id ) = split /:/, $target_value;
    return if not defined $target_owner_comp_id;
    $target_id = 0 if not defined $target_id;

    my $src_target_node;

    if ( $target_id eq 'cancel_last_action' ) {
      $src_target_node = $doc->createElement ( "target" );
      $src_target_node->setAttribute ( "id", 0 );
      $src_target_node->setAttribute ( "comp_id", $target_owner_comp_id );
    } else {
      $src_target_node = $source_doc->findnodes (
						 '/components/component[@id='.$target_owner_comp_id.']/action/target[@id='.$target_id.']'
						)->get_node(1)->cloneNode(1);
    }

    $target_sequence->appendChild($src_target_node);
  }

  return $target_sequence;
}

sub create_section {
  my ( $doc, $cgi_query, $compid, $sectid, $source_doc ) = @_;
  my $section = $doc->createElement ( "section" );
  my $source_sec = $source_doc->find ( '//component[@id='.$compid.']/section[@id='.$sectid.']' )->pop();
  $section->setAttribute( "id", $sectid );
  $section->setAttribute( "active_subsection_id", $cgi_query->param ( "active_subsection_id___c_".$compid."_s_".$sectid ) );
  $section->setAttribute( "submit_all_subsections", $source_sec->findvalue( '@submit_all_subsections' ) );

  foreach ( $cgi_query->param ( "subsection___c_".$compid."_s_".$sectid ) ) {
    $section->appendChild ( create_subsection ( $doc, $cgi_query, $compid, $sectid, $_, $source_sec ) );
  }

  my $re = "___c_".$compid."_s_".$sectid;
  foreach ( grep ( /$re$/, $cgi_query->param ) ) {
    if ( !/subsection___/ ) {
      $section->appendChild ( create_submit ( $doc, $cgi_query, $_, $source_sec ) );
    }
  }

  return $section;
}

sub create_subsection {
  my ( $doc, $cgi_query, $compid, $sectid, $subsectid, $source_sec ) = @_;
  my $subsection = $doc->createElement ( "subsection" );
  $subsection->setAttribute( "id", $subsectid );

  my $re = "___c_".$compid."_s_".$sectid."_su_".$subsectid;
  foreach ( grep ( /$re/, $cgi_query->param ) ) {
    $subsection->appendChild ( create_submit ( $doc, $cgi_query, $_, $source_sec->find ( 'subsection[@id='.$subsectid.']' )->get_node(1) ) );
  }

  return $subsection;
}

sub create_submit
    {
    my ( $doc, $cgi_query, $submit_name, $source_sec ) = @_;
    my $submit = $doc->createElement ( "submit" );
    $submit->setAttribute ( "name", $submit_name );

    my @submit_name_ = split /___/, $submit_name;

    my $is_required = $source_sec->findvalue ( './/@required[../@name=\''.$submit_name_[0].'\']' );
    my $type     = $source_sec->findvalue ( './/@type[../@name=\''.$submit_name_[0].'\']' );
#   print STDERR "$submit_name_[0] ::: ", $is_required, "\n";

    my @values = $cgi_query->param ( $submit_name );

    if ( $is_required eq "1" and ( join ( "", @values ) eq "" ) )
        {
        $submit->setAttribute ( "missing", 1 );
        return $submit;
        }

    foreach (@values)
        {
        next if $_ eq "";
        my $value = $doc->createElement("value");
        my $this = $_;

        if ( not $this =~ /\n/ )
            {
            if ( -f $this and $type eq "file" )
                {
                $value->setAttribute( "file", 1 );

                my $buf;
                my $fh = $cgi_query->upload($submit_name);
                my $file_suffix = $this;
                $file_suffix =~ s/.*\.//g;
                my $tmp_fh = new File::Temp( DIR => $EP::Config::h->{TMP}, SUFFIX => ".$file_suffix", UNLINK => 0 );
                # kind of hack, but would work: re-write $this to make sure temp file name is written in xml
                $this = $tmp_fh->filename;

                print $tmp_fh $buf while sysread $fh, $buf, 100000;
                close $tmp_fh;
                }
            }

        my $value_text = $doc->createTextNode ( $this );
        $value->appendChild ( $value_text );
        $submit->appendChild ( $value );
        }

    return $submit;
    }

sub error {
  my $error = shift;
  print STDERR "Error in eval: ";
  if (ref $error) {
    print STDERR "msg: ", $error->getMessage();
    if (ref $error eq 'XML::Xerces::DOM_DOMException') {
      print STDERR "\n\tcode: ", $error->{code};
    }
    print STDERR "\n";
  } else {
    print STDERR $error;
  }
}

1;
